<?php

namespace yunj\app\admin\service\route;

use think\db\BaseQuery;
use think\facade\Db;
use yunj\app\admin\enum\EnableLoginAuth;
use yunj\app\admin\enum\IsDemo;
use yunj\app\admin\enum\RedisKey;
use yunj\app\admin\enum\route\RouteDataType;
use yunj\app\admin\enum\State;

class RouteService extends Service {

    /**
     * 获取路由数据
     * @param int|string|mixed $routeId
     * @return array
     */
    public static function getRouteDataById($routeId): array {
        $routes = self::getAllRoutes();
        return $routes[$routeId] ?? [];
    }

    /**
     * 获取有效的路由数据
     * @param int|string|mixed $routeId
     * @return array
     */
    public static function getValidRouteDataById($routeId): array {
        $routes = self::getValidRoutes();
        return $routes[$routeId] ?? [];
    }

    /**
     * 获取所有有效（正常）的路由数据
     * @param bool $static 是否获取静态数据
     * @return array
     */
    public static function getValidRoutes(bool $static = true): array {
        if ($static) {
            static $routes;
            if (isset($routes)) {
                return $routes;
            }
        }
        $routes = [];
        $allRoutes = self::getAllRoutes();
        foreach ($allRoutes as $id => $route) {
            if ($route['final_state'] == State::NORMAL) {
                $routes[$id] = $route;
            }
        }
        return $routes;
    }

    /**
     * 获取所有的路由数据（含正常、回收站）
     * @param bool $regain 是否重新获取
     * @param bool $static 是否获取静态数据
     * @return array
     */
    public static function getAllRoutes(bool $regain = false, bool $static = true): array {
        if (!$regain && $static) {
            static $routes;
            if (isset($routes)) {
                return $routes;
            }
        }
        // 从缓存获取
        if (!$regain) {
            $routes = self::getCacheValue();
            if ($routes && is_array($routes)) {
                return $routes;
            }
        }

        // 重新获取
        $field = ['id', 'group_id', 'name', 'desc', 'full_desc', 'rule', 'base_url', 'route', 'middleware', 'enable_login_auth', "if(system_key is null,0,1) as is_system", 'state'];
        $routes = self::getAdminRouteModel()->getOwnRowsToArray([
            ['rule', '<>', ''],
            ['route', '<>', ''],
            ['state', '<>', State::DELETED],
        ], $field);

        $routes = array_column($routes, null, 'id');
        foreach ($routes as &$route) {
            self::handleRoute($route);
        }
        // 设置缓存 todo
//        self::setCacheValue($routes);

        return $routes;
    }

    /**
     * 重置
     */
    public static function reset(): void {
        // 处理所有数据完整属性值
        self::handleAllDatasFullAttr();
        // 路由文件重置（内部执行了缓存清理）
        RouteFileService::reset();
        // 重新获取
        self::getAllRoutes(true);
    }

    /**
     * 获取缓存值
     * @return bool|array|mixed
     */
    public static function getCacheValue() {
        /** @var RedisKey $keyObj */
        $keyObj = RedisKey::ADMIN_ROUTES();
        $key = $keyObj->key();
        $redis = redis();
        // redis获取
        $res = $redis->get($key);
        if ($res !== false && $res !== null) {
            if (is_json($res, $resData, true)) {
                $res = $resData;
            }
            return $res;
        }
        return false;
    }

    /**
     * 设置缓存值（不过期）
     * @param array|mixed $value
     */
    private static function setCacheValue($value): void {
        if (!is_string($value)) {
            $value = json_encode($value, JSON_UNESCAPED_UNICODE);
        }
        /** @var RedisKey $keyObj */
        $keyObj = RedisKey::ADMIN_ROUTES();
        $key = $keyObj->key();
        $redis = redis();
        $redis->set($key, $value);
    }

    // 处理单个路由
    private static function handleRoute(array &$route) {
        $group = self::getGroupData($route['group_id']);
        // 处理路由规则
        self::handleRouteRule($route, $group);
        // 处理路由地址
        self::handleRouteRoute($route, $group);
        // 处理路由最终的属性值
        self::handleFinalAttrData($route, $group);
    }

    // 处理路由规则
    private static function handleRouteRule(array &$route, array $group) {
        $rule = $route['rule'];
        if ($group) {
            $rule = handle_base_url($group['name'] . '/' . $rule);
        }
        $route['rule'] = $rule;
    }

    // 处理路由地址
    private static function handleRouteRoute(array &$route, array $group) {
        $routeVal = $route['route'];
        if (strstr($routeVal, '@') || strstr($routeVal, '::')) {
            // 路由到类的方法
            if ($group) {
                $routeVal = $group['namespace'] . '\\' . $routeVal;
            }
            $routeVal = '\\' . $routeVal;    // 在开头加上'\'
            $route['route'] = preg_replace('/\\\+/', '\\', $routeVal); // 去掉连续的'\'
        }
    }


    // 获取分组数据（含正常和回收站数据）
    private static function getGroupData($groupId): array {
        static $groups;
        if (!isset($groups)) {
            $field = ['id', 'pid', 'name', 'desc', 'namespace', 'middleware', 'enable_login_auth', 'state'];
            $dbDatas = self::getAdminRouteGroupModel()->getOwnRowsToArray([['state', '<>', State::DELETED]], $field);
            $groups = [];
            // 处理分组数据
            self::handleGroupData($groups, null, $dbDatas);
        }
        return $groups[$groupId] ?? [];
    }

    // 处理分组数据
    private static function handleGroupData(array &$groups, ?array $parentGroup, array $dbDatas): void {
        $parentGroup = $parentGroup ?: [
            'id' => 0,
            'name' => '',
            'parent_final_name' => '',
            'final_name' => '',
            'enable_login_auth' => EnableLoginAuth::NIL,
            'parent_final_enable_login_auth' => EnableLoginAuth::NIL,
            'final_enable_login_auth' => EnableLoginAuth::NIL,
            'state' => State::NORMAL,
            'parent_final_state' => State::NORMAL,
            'final_state' => State::NORMAL,
        ];
        foreach ($dbDatas as $dbData) {
            if ($dbData['pid'] != $parentGroup['id']) continue;
            $group = $dbData;
            $group['parent_final_name'] = $parentGroup['final_name'];
            $group['final_name'] = handle_base_url($parentGroup['final_name'] . '/' . $group['name']);
            self::handleFinalAttrData($group, $parentGroup);
            $groups[$group['id']] = $group;
            self::handleGroupData($groups, $group, $dbDatas);
        }
    }

    // 处理最终的属性数据
    public static function handleFinalAttrData(array &$item, ?array $parentItem) {
        $parentItem = $parentItem ?: [
            'enable_login_auth' => EnableLoginAuth::NIL,
            'parent_final_enable_login_auth' => EnableLoginAuth::NIL,
            'final_enable_login_auth' => EnableLoginAuth::NIL,
            'state' => State::NORMAL,
            'parent_final_state' => State::NORMAL,
            'final_state' => State::NORMAL,
        ];
        // enable_login_auth
        $item['parent_final_enable_login_auth'] = $parentItem['final_enable_login_auth'];
        $item['final_enable_login_auth'] = $item['enable_login_auth'] != EnableLoginAuth::NIL ? $item['enable_login_auth'] : $item['parent_final_enable_login_auth'];
        // state
        $item['parent_final_state'] = $parentItem['final_state'];
        $item['final_state'] = $item['parent_final_state'] != State::NORMAL ? $item['parent_final_state'] : $item['state'];
    }

    /**
     * 处理所有数据的完整属性值（补充分组、路由、路由请求的完整描述、路由的base_url）
     */
    private static function handleAllDatasFullAttr() {
        $allItems = self::getAllItemsBuilderQuery()->where([['state', '<>', State::DELETED]])->select()->toArray();
        $datas = [];
        self::setFullAttrDatas($datas, [''], $allItems);
        $dbDatas = [];
        foreach ($datas as $data) {
            ['type' => $type, 'id' => $id, 'full_desc' => $fullDesc, 'base_url' => $baseUrl] = $data;
            $idArr = explode('_', $id);
            $dbData = [
                'id' => $idArr[1],
                'full_desc' => $fullDesc
            ];
            switch ($type) {
                case RouteDataType::GROUP:
                    $dbData['full_name'] = trim($baseUrl, '/');
                    break;
                case RouteDataType::ROUTE:
                    $dbData['base_url'] = $baseUrl;
                    break;
            }
            $dbDatas[$type] = $dbDatas[$type] ?? [];
            $dbDatas[$type][] = $dbData;
        }
        foreach ($dbDatas as $type => $typeDbDatas) {
            /** @var RouteDataType $typeObj */
            $typeObj = RouteDataType::byValue($type);
            $typeObj->getModel()->batchChange($typeDbDatas);
        }
    }

    private static function setFullAttrDatas(array &$datas, array $pids, array $allItems): void {
        $newPids = [];
        foreach ($allItems as $k => $item) {
            ['type' => $type, 'id' => $id, 'pid' => $pid, 'desc' => $desc, 'name' => $name] = $item;
            if (!in_array($pid, $pids, true)) {
                continue;
            }
            $parentData = $datas[$pid] ?? [];
            $parentFullDesc = $parentData['full_desc'] ?? '';
            $parentBaseUrl = $parentData['base_url'] ?? '/';
            $datas[$id] = [
                'type' => $type,
                'id' => $id,
                'full_desc' => $parentFullDesc . $desc,
                'base_url' => handle_base_url($parentBaseUrl . '/' . $name),
            ];
            if ($type != RouteDataType::REQUEST) {  // 路由请求没有下级
                $newPids[] = $id;
            }
        }
        if ($newPids) {
            self::setFullAttrDatas($datas, $newPids, $allItems);
        }
    }


    /**
     * 获取所有数据的联合查询query
     * @return BaseQuery
     */
    public static function getAllItemsBuilderQuery() {
        $prefix = Db::connect()->getConfig('prefix');
        $routeType = RouteDataType::ROUTE;
        $routeTypeTxt = RouteDataType::ROUTE()->getTitle();
        $groupType = RouteDataType::GROUP;
        $groupTypeTxt = RouteDataType::GROUP()->getTitle();
        $requestType = RouteDataType::REQUEST;
        $requestTypeTxt = RouteDataType::REQUEST()->getTitle();
        $query = Db::table("(
            select '{$routeType}' as type,'{$routeTypeTxt}' as type_txt,concat('{$routeType}_',id) as id,if(group_id > 0,concat('{$groupType}_',group_id),'') as pid,`desc`,full_desc,base_url,rule as name,route,'' as method,middleware,sort,if(system_key is null,0,1) as is_system,is_demo,create_time,last_update_time,state from {$prefix}admin_route 
            union all
            select '{$groupType}' as type,'{$groupTypeTxt}' as type_txt,concat('{$groupType}_',id) as id,if(pid > 0,concat('{$groupType}_',pid),'') as pid,`desc`,full_desc,'' as base_url,name,namespace as route,'' as method,middleware,sort,if(system_key is null,0,1) as is_system,is_demo,create_time,last_update_time,state from {$prefix}admin_route_group
            union all 
            select '{$requestType}' as type,'{$requestTypeTxt}' as type_txt,concat('{$requestType}_',id) as id,concat('{$routeType}_',route_id) as pid,`desc`,full_desc,'' as base_url,`desc` as name,'' as route,method,'' as middleware,sort,if(system_key is null,0,1) as is_system,is_demo,create_time,last_update_time,state from {$prefix}admin_route_request 
        ) as tmp");
        return $query;
    }

    /**
     * 校验单个中间件
     * @param $item
     * @return bool
     */
    public static function checkMiddlewareItem($item): bool {
        return is_array($item) && ($class = $item['class'] ?? '') && class_exists($class);
    }

}